2023年 7月 11日
内容纲要
1. Vite 的基本使用1.1 浏览器内的模板编译 vs 构建工具1. 什么是浏览器内的模板编译2. 通过构建工具进行预构建3. 主流的构建工具1.2 基于 Vite 创建 Vue3 项目1.3 熟悉项目的目录结构1.4 以 Compiler + Runtime 的方式编写代码1.5 以 Runtime 的方式编写代码2. Vue 组件的基本使用2.1 组件的概念1. 什么是组件2. 定义一个组件3. 使用组件4. 组件之间的关系5. script setup 语法糖2.2 混合使用选项式 API 和组合式 API1. 普通 script + setup 入口函数2. 普通 script + script setup3. script setup + defineOptions 宏函数2.3 SFC 中的样式1. 样式冲突问题2. 解决样式冲突3. 根元素与 scoped4. 深度选择器5. CSS 中的 v-bind6. 基于组合式 API 封装自己的 Hooks API7. 启用 less 语法2.4 组件注册1. 全局注册组件2. 局部注册组件3. 组件名格式2.5 动态组件 & KeepAlive1. component 元素和 is 属性2. 使用 KeepAlive 保持组件的状态3. 缓存实例的生命周期4. 最大缓存实例数5. 包含 Or 排除6. 动态添加或移除被缓存的组件
版权归作者 ©刘龙宾 所有,本文章未经作者允许,禁止私自转载!
浏览器内的模板编译,指的就是把浏览器不识别的代码,编译成浏览器能识别和运行的代码。例如下面的 v-for 指令:
xxxxxxxxxx51<ul>2 <li v-for="item in todos">3 <span>{{ item.task }}</span>4 </li>5</ul>浏览器无法正确解析和执行 v-for 指令,因为这是 Vue 框架提供的特殊语法。因此,为了保证浏览器最终能渲染出列表的结构,Vue 必须做以下2件事情:
图示如下:

我们之前在浏览器中引入的 vue3.global.vue 就完整的包含了 Compiler 和 Runtime。
也就是说,浏览器在真正调用 Runtime-dom 运行网页之前,必须先调用 compiler-dom 进行模板的编译,这就带来了以下两个问题:
为了解决模板编译耗时问题和导入的 vue.js 包体积大的问题,我们推荐大家通过构建工具对 Vue 项目进行预构建。构建工具的作用如下图所示:

通过预构建,我们可以把模板编译的过程,独立于项目运行之前。这样做的好处有以下两点:
目前,市面上主流的构建工具有以下2个:
其中 webpack 是一个老牌的前端工程化的构建工具,功能强大,插件众多,生态完善。支持 Vue,React,Angular 等主流的前端框架。
而 vite 是 Vue 官方团队研发出的前端工程化构建工具,以卓越的构建性能而迅速崛起,同样支持 Vue,React,Angular 等主流的前端框架。
在今后的 Vue3 项目中,我们选择 vite 作为项目的构建工具,主要原因有以下两点:
参考文档: 为什么选 Vite
打开终端,运行如下的 npm 命令,基于命令行的方式快速创建 vue3 的项目:
xxxxxxxxxx11npm create vite@latest通过对终端进行交互式的操作,选择项目的配置:
xxxxxxxxxx61Need to install the following packages:2 create-vite@4.3.23Ok to proceed? (y) y4√ Project name: ... code25√ Select a framework: » Vue6√ Select a variant: » JavaScript项目创建完成后,需要分别执行以下3个命令,从而把项目运行起来:
xxxxxxxxxx31cd code22npm install3npm run dev认识
根目录下
重要的
文件
和
文件夹
:
dev 和 build 两个命令。认识 src 目录下的文件和文件夹:
在 index.html 中,编写模板结构:
xxxxxxxxxx31<div id="app">2 <h1>{{ name }}</h1>3</div>在 src/main.js 中,创建 app 实例,并提供 data 数据如下:
xxxxxxxxxx111import { createApp } from 'vue'23const app = createApp({4 data() {5 return {6 name: 'zs'7 }8 }9})1011app.mount('#app')此时,会发现浏览器的终端提示了如下的警告消息:
xxxxxxxxxx21[Vue warn]: Component provided template option but runtime compilation is not supported in this build of Vue. Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".2 at 意思是:步骤2中第1行导入的 vue,只包含 Runtime 运行时,不包含 Compiler 编译器,所以无法在浏览器中编译步骤1中的模板结构。为了能够支持浏览器内的模板编译,请把导入的 vue 替换为 vue/dist/vue.esm-bundler.js。
清楚了问题所在之后,我们把 src/main.js 中导入 vue 的代码进行修改:
xxxxxxxxxx11import { createApp } from 'vue/dist/vue.esm-bundler.js'保存之后,我们发现项目可以正常的运行了。但是,这种运行 vue 项目的方式并没有把构建工具的作用体现出来:
这样做的好处是:
Vue 建议我们把模板和数据,全部封装到 .vue 文件中。这样可以充分发挥构建工具的作用,对整个项目中的 .vue 文件进行预构建(模板编译)。当我们在运行项目的时候,只需要导入 Runtime 版本的 vue.js 即可。
一个 .vue 文件的基本结构如下:
xxxxxxxxxx81<!-- 结构 -->2<template></template>34<!-- 行为 -->5<script></script>67<!-- 样式 -->8<style></style>所以,我们可以把 1.4 小节中的功能,封装到 src/App.vue 中,具体代码如下:
xxxxxxxxxx291<!-- 模板结构 -->2<template>3 <h1>{{ name }}</h1>4</template>56<!-- 行为 -->7<script>8import { ref } from 'vue'910export default {11 // setup 函数是响应式 API 的入口12 setup() {13 // 定义响应式数据14 const name = ref('liulongbin')1516 // 把数据暴露给模板使用17 return {18 name19 }20 }21}22</script>2324<!-- 样式 -->25<style>26h1 {27 color: red;28}29</style>最后,需要修改 src/main.js 中的代码如下:
xxxxxxxxxx101// 1. 导入只包含 Runtime 运行时的 vue2import { createApp } from 'vue'3// 2. 导入要渲染到页面上的 .vue 文件(组件)4import App from './App.vue'56// 3. 调用 createApp() 函数,并指定要渲染“组件”7const app = createApp(App)89// 4. 指定挂载的区域10app.mount('#app')组件是对 UI 结构的封装和复用。在实际应用中,组件常常被组织成层层嵌套的树状结构:

在 Vue 项目中,组件通常被定义为以 .vue 结尾的文件。我们可以在 .vue 组件内封装自定义的内容与逻辑。
当使用 vite 等构建工具时,我们通常会把组件定义在一个单独的 .vue 文件中,这被叫做单文件组件(Single File Component,简称为:SFC)。例如,下面的代码定义了名为 ButtonCounter.vue 的组件:
xxxxxxxxxx241<!-- 模板结构 -->2<template>3 <h2>当前的 count 值为:{{ count }}</h2>4 <button @click="add">+1</button>5</template>67<!-- 行为逻辑 -->8<script>9import { ref } from 'vue'1011export default {12 setup() {13 // 响应式数据14 const count = ref(0)15 // 方法事件处理器16 const add = () => count.value++1718 return {19 count,20 add21 }22 }23}24</script>假设我们要在 App.vue 组件中,使用刚才定义的 ButtonCounter.vue 组件,我们可以在 App.vue 中使用 import 导入 ButtonCounter 组件:
xxxxxxxxxx11import ButtonCounter from './components/ButtonCounter.vue'然后,在 components 节点下注册组件:
xxxxxxxxxx61export default {2 setup() { /* 省略其它代码 */ },3 components: {4 ButtonCounter5 }6}最后,在模板结构中以标签的形式,使用刚才注册的 ButtonCounter 组件:
xxxxxxxxxx71<!-- 结构 -->2<template>3 <h1>{{ name }}</h1>4 <hr />5 <!-- 使用 ButtonCounter 组件 -->6 <ButtonCounter></ButtonCounter>7</template>组件之间的关系分为:

注意:有直接嵌套关系的,属于父子关系;若是间接的嵌套关系,则属于后代关系。
在 SFC 中,为了简化 setup 入口函数的代码,Vue 推荐使用 script setup 语法糖,它的格式如下:
xxxxxxxxxx51<template></template>23<script setup></script>45<style></style>例如,使用 script setup 语法糖把 ButtonCounter.vue 组件改造如下:
xxxxxxxxxx131<template>2 <h1>当前的 count 值为:{{ count }}</h1>3 <button @click="add">+1</button>4</template>56<script setup>7import { ref } from 'vue'8// 顶层变量,方法等都可以在模板中被访问到9const count = ref(0)10const add = () => count.value++11</script>1213<style></style>同时,在 App.vue 组件中使用 ButtonCounter.vue 组件时,可以改造成 script setup 的写法:
xxxxxxxxxx141<!-- 结构 -->2<template>3 <h1>{{ name }}</h1>4 <hr />5 <!-- 使用 ButtonCounter 组件 -->6 <ButtonCounter></ButtonCounter>7</template>89<script setup>10 import { ref } from 'vue'11 // 顶层变量,都可以在模板中直接使用12 import ButtonCounter from './components/ButtonCounter.vue'13 const name = ref('liulongbin')14</script>xxxxxxxxxx231<template>2 <h2>当前的 count 值为:{{ count }}</h2>3 <button @click="add">+1</button>4</template>56<script>7import { ref } from 'vue'89export default {10 // 其它选项式 API11 name: 'BtnCount',12 // 组合式 API 的入口13 setup() {14 const count = ref(0)15 const add = () => count.value++1617 return {18 count,19 add20 }21 }22}23</script>xxxxxxxxxx161<template>2 <h2>当前的 count 值为:{{ count }}</h2>3 <button @click="add">+1</button>4</template>56<script setup>7import { ref } from 'vue'8const count = ref(0)9const add = () => count.value++10</script>1112<script>13export default {14 name: 'BtnCount'15}16</script>xxxxxxxxxx161<template>2 <h2>当前的 count 值为:{{ count }}</h2>3 <button @click="add">+1</button>4</template>56<script setup>7// 宏函数,不需要导入就能直接使用8// Vue 在编译阶段,会把 defineOptions 中的配置,编译为选项式 API9defineOptions({10 name: 'BtnCount'11})1213import { ref } from 'vue'14const count = ref(0)15const add = () => count.value++16</script>默认情况下,在组件中声明的 <style> 属于全局样式,这些样式除了会影响组件自身的元素,还会影响到其它组件中的元素。因为这些组件经过 Vue 编译之后,都会被呈现在 index.html 页面中,所以很容易出现样式冲突的问题。
例如 App.vue 组件中给 h1 标签定义的样式,会影响到 Son.vue 组件中的 h1。相应的,在 Son.vue 组件中为 h1 标签定义的样式,也会影响到 App.vue 组件中的 h1。
App.vue 组件中的代码如下:
xxxxxxxxxx191<!-- 结构 -->2<template>3 <h1>这是 App 根组件中的标题</h1>45 <hr />6 <Son></Son>7</template>89<!-- 行为 -->10<script setup>11import Son from './components/Son.vue'12</script>1314<!-- 样式 -->15<style>16h1 {17 color: red;18}19</style>Son.vue 组件中的样式如下:
xxxxxxxxxx101<template>2 <h1>这是 Son 子组件中的标题</h1>3 <p>这是 Son 子组件中的段落</p>4</template>56<style>7h1 {8 font-size: 16px;9}10</style>解决样式冲突的方式有很多种,在这里,我们使用自定义属性 + 属性选择器的方式来解决组件之间样式冲突的问题。实现思路如下:
例如,给 App.vue 指定一个自定义属性 data-v-001,则把 App.vue 中的模板结构和样式修改如下:
xxxxxxxxxx191<!-- 结构 -->2<template>3 <h1 data-v-001>这是 App 根组件中的标题</h1>45 <hr data-v-001 />6 <Son></Son>7</template>89<!-- 行为 -->10<script setup>11import Son from './components/Son.vue'12</script>1314<!-- 样式 -->15<style>16h1[data-v-001] {17 color: red;18}19</style>给 Son.vue 指定一个自定义属性 data-v-002,则把 Son.vue 中的模板结构和样式修改如下:
xxxxxxxxxx101<template>2 <h1 data-v-002>这是 Son 子组件中的标题</h1>3 <p data-v-002>这是 Son 子组件中的段落</p>4</template>56<style>7h1[data-v-002] {8 font-size: 16px;9}10</style>这样,就成功的解决了组件之间的样式冲突问题。
因为每个组件中的样式,都有添加了唯一的属性选择器,从而保证了当前组件的样式只会影响自身的元素。
其实 Vue 为了简化用户的编码复杂度,为 <style> 提供了 scoped 选项。Vue 在编译 SFC 模板的时候,如果发现 <style scoped> 的写法,就会自动给当前组件生成一个唯一的 data-v-* 属性选择器,并自动把属性选择器添加到每个元素和每个CSS选择器上。
在 Vue3 中的 SFC 组件内,<template> 区域允许存在多个根元素,同时,也支持 Vue2 中的唯一根元素的写法。当父子组件同时应用 scoped 时:
<template> 中存在多个根元素,不会把父组件的 data-v-* 添加给子组件的根元素。<template> 中只有唯一的根元素,则会把父组件的 data-v-* 添加给子组件的根元素。当父组件开启了 <style> 标签的 scoped 选项之后,默认情况下,父组件中编写的样式无法影响到子组件,这完全符合防止组件之间样式冲突的需求。
但是,这也产生了一个负面的作用:在不改动子组件代码的情况下,父组件中无法微调子组件中的任何样式!
解决方案很简单,使用 :deep() 这个伪类,把子组件的选择器包裹起来即可,例如:
xxxxxxxxxx111<!-- 样式 -->2<style scoped>3h1 {4 color: red;5}67/* 把子组件的选择器,使用 :deep() 包裹起来 */8:deep(p) {9 color: green;10}11</style>最终生成的样式如下:
xxxxxxxxxx31[data-v-7a7a37b1] p {2 color: green;3}SFC 中的 <style> 标签支持使用 v-bind() 这个 CSS 函数将响应式数据绑定为 CSS 属性的值。例如把响应式数据 count 绑定为 h1 元素的 font-size 值:
xxxxxxxxxx251<!-- 结构 -->2<template>3 <!-- 点击按钮,让 count 数值自增 +1 -->4 <button @click="count++">+1</button>5 <h1>这是 App 根组件中的标题</h1>6</template>78<!-- 行为 -->9<script setup>10import { ref, computed } from 'vue'1112// 字体大小,初始值为数值 12,不带 px 单位13const count = ref(12)14// 计算属性:为字体大小添加 px 单位值15const fsize = computed(() => count.value + 'px')16</script>1718<!-- 样式 -->19<style scoped>20h1 {21 color: red;22 /* 把 font-size 的值绑定为计算属性 fsize 的值 */23 font-size: v-bind(fsize);24}25</style>经过我们的分析,发现 2.3.5 小节中,为了保证功能的正常运行,我们只需要拿到 count 和 fsize 即可。其中:
px 单位的字符串;我们会把它绑定为元素的 font-size 属性值因此,我们可以把 count 和 fsize 创建的过程,封装为自定义的 Hooks。过程如下:
在 src 目录下创建 hooks 文件夹,并在 hooks 文件夹下新建名为 index.js 的文件:
xxxxxxxxxx131// 1. 导入需要的组合式 API 函数2import { ref, computed } from 'vue'34// 2. 向外导出一个名为 useXXX 的自定义 Hooks 函数,其中 n 为初始数据5export const useSizePx = (n) => {6 // 2.1 定义响应式数据7 const size = ref(n)8 // 2.2 定义依赖于 size 的 computed 计算属性9 const sizePx = computed(() => size.value + 'px')1011 // 3. 以数组的形式,向外暴露 size 和 sizePx 两份数据,供用户使用12 return [size, sizePx]13}在 App.vue 中导入自定义的 Hooks 函数,传入初始值,并使用数组的解构操作,得到 Hooks 返回的 ref 数据和 computed 计算属性:
xxxxxxxxxx191<!-- 结构 -->2<template>3 <button @click="count++">+1</button>4 <h1>这是 App 根组件中的标题</h1>5</template>67<!-- 行为 -->8<script setup>9import { useSizePx } from './hooks/index.js'10const [count, fsize] = useSizePx(12)11</script>1213<!-- 样式 -->14<style scoped>15h1 {16 color: red;17 font-size: v-bind(fsize);18}19</style>自定义 Hooks 的好处是:实现了业务代码的封装和复用。
在 SFC 中编写样式时,如果想要使用 less 语法编写 CSS 样式,需要按照以下两个步骤进行配置:
步骤1:运行以下的 npm 命令,安装 less 依赖包:
xxxxxxxxxx11npm install less -D步骤2:为 <style> 标签添加 lang="less" 属性:
xxxxxxxxxx101<style lang="less" scoped>2h1 {3 color: red;4 font-size: v-bind(fsize);56 span {7 color: cyan;8 }9}10</style>然后,就可以愉快的使用 less 语法啦~
我们可以使用 Vue 应用实例的 app.component() 方法,让组件在当前 Vue 应用中全局可用,语法如下:
xxxxxxxxxx11app.component('注册名称', 要注册的组件)例如:
xxxxxxxxxx31import Counter from './components/Counter.vue'2// 使用 app.component() 函数注册全局组件3app.component('ButtonCounter', Counter)注册好的组件,可以在任何组件中被使用:
xxxxxxxxxx11<ButtonCounter></ButtonCounter>在选项式 API 中,需要按照以下3个步骤注册局部组件(私有组件):
components 选项下注册导入的组件例如下面的代码,就使用了选项式 API 注册了 ButtonCounter 子组件:
xxxxxxxxxx161<template>2 <!-- 3. 使用 -->3 <ButtonCounter></ButtonCounter>4</template>56<script>7// 1. 导入8import ButtonCounter from './components/ButtonCounter.vue'910export default {11 // 2. 注册12 components: {13 ButtonCounter14 }15}16</script>在 script setup 中,免去了注册组件的步骤,因为顶层变量可以直接在模板中使用:
xxxxxxxxxx91<template>2 <!-- 2. 使用 -->3 <ButtonCounter></ButtonCounter>4</template>56<script setup>7// 1. 导入8import ButtonCounter from './components/ButtonCounter.vue'9</script>注意:在父组件中局部注册的子组件,只能在父组件中使用!
无论是全局注册还是局部注册组件,都需要提供组件的注册名称。Vue 官方推荐使用 PascalCase(大驼峰) 格式的注册名称。
因为 PascalCase 格式的名称能很好的把 HTML 标签和自定义组件区分开来。
例如 <ButtonCounter></ButtonCounter> 就是使用了 PascalCase 格式的组件名称。
不过呢,以 PascalCase 格式注册或导入的组件,也能基于 kebab-case 格式的标签进行使用,例如 <button-counter></button-counter>。
注意:推荐大家今后在导入组件、注册组件、使用组件时都统一使用大驼峰的方式。
当我们需要在同一个位置切换展示不同的组件时,可以使用动态组件来实现此功能。
component 元素是一个组件的占位符,具体在 component 元素所在的位置展示哪个组件,由 is 属性决定。
is 属性的值可以是:
例如,定义 ComLight.vue 组件:
xxxxxxxxxx141<template>2 <div class="box"></div>3</template>45<style scoped>6.box {7 width: 500px;8 height: 300px;9 background-color: #efefef;10 box-shadow: 1px 1px 10px #ccc;11 border-radius: 10px;12 padding: 10px;13}14</style>再定义 ComDark.vue 组件:
xxxxxxxxxx151<template>2 <div class="box"></div>3</template>45<style scoped>6.box {7 width: 500px;8 height: 300px;9 background-color: #2b2b2b;10 box-shadow: 1px 1px 10px #888;11 border-radius: 10px;12 padding: 10px;13 color: white;14}15</style>在 App.vue 中通过 v-if 指令实现组件的按需展示:
xxxxxxxxxx161<template>2 <button @click="flag = false">Light</button>3 <button @click="flag = true">Dark</button>45 <hr />6 <ComLight v-if="!flag"></ComLight>7 <ComDark v-else></ComDark>8</template>910<script setup>11import { ref } from 'vue'12import ComLight from './components/ComLight.vue'13import ComDark from './components/ComDark.vue'1415const flag = ref(false)16</script>在 App.vue 中通过 :is 指定导入的组件对象,从而实现组件的按需展示:
xxxxxxxxxx161<template>2 <button @click="flag = false">Light</button>3 <button @click="flag = true">Dark</button>45 <hr />6 <component :is="flag ? ComDark : ComLight"></component>7</template>89<script setup>10import { ref } from 'vue'11// ComLight 和 ComDark 是导入的组件对象12import ComLight from './components/ComLight.vue'13import ComDark from './components/ComDark.vue'1415const flag = ref(false)16</script>在 App.vue 中通过 :is 指定注册的组件名称,从而实现组件的按需展示:
xxxxxxxxxx251<template>2 <button @click="flag = false">Light</button>3 <button @click="flag = true">Dark</button>45 <hr />6 <component :is="flag ? 'MyDark' : 'MyLight'"></component>7</template>89<script>10import ComLight from './components/ComLight.vue'11import ComDark from './components/ComDark.vue'1213export default {14 data() {15 return {16 flag: false17 }18 },19 // MyLight 和 MyDark 是注册名称20 components: {21 MyLight: ComLight,22 MyDark: ComDark23 }24}25</script><KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
在 ComLight.vue 组件中新增计数器功能:
xxxxxxxxxx221<template>2 <div class="box">3 <h1>{{ count }}</h1>4 <button @click="count++">+1</button>5 </div>6</template>78<script setup>9import { ref } from 'vue'10const count = ref(0)11</script>1213<style scoped>14.box {15 width: 500px;16 height: 300px;17 background-color: #efefef;18 box-shadow: 1px 1px 10px #ccc;19 border-radius: 10px;20 padding: 10px;21}22</style>在 ComDark.vue 组件中新增文本框的双向数据绑定:
xxxxxxxxxx231<template>2 <div class="box">3 <input type="text" v-model="msg" />4 <p>消息是:{{ msg }}</p>5 </div>6</template>78<script setup>9import { ref } from 'vue'10const msg = ref('')11</script>1213<style scoped>14.box {15 width: 500px;16 height: 300px;17 background-color: #2b2b2b;18 box-shadow: 1px 1px 10px #888;19 border-radius: 10px;20 padding: 10px;21 color: white;22}23</style>最后,在 App.vue 根组件中,使用 <KeepAlive> 组件把 <component :is> 的动态组件包裹起来,即可实现组件的缓存:
xxxxxxxxxx31 <KeepAlive>2 <component :is="flag ? 'MyDark' : 'MyLight'"></component>3 </KeepAlive>被 KeepAlive 的组件,会有两种状态:被缓存或被激活。如果程序员想监听这两种状态的切换,并执行特定的操作,此时可以使用 onActivated(fn) 和 onDeactivated(fn) 这两个组合式 API 进行监听。
onDeactivated(fn) 会为当前组件注册一个被缓存时的回调,当组件从被激活的状态切换至被缓存的状态时,或被卸载时,会触发这个回调的执行。
onActivated(fn) 会为当前组件注册一个被激活时的回调,当组件首次被创建时,或组件从被缓存的状态切换至被激活的状态时,会触发这个回调的执行。
例如,在 ComDark.vue 组件被激活时,让文本框自动获取光标的焦点:
xxxxxxxxxx391<template>2 <div class="box">3 <!-- 为文本框添加 ref 引用 -->4 <input type="text" v-model="msg" ref="iptRef" />5 <p>消息是:{{ msg }}</p>6 </div>7</template>89<script setup>10// 按需导入 onActivated 和 onDeactivated 两个组合式 API11import { ref, onActivated, onDeactivated } from 'vue'12const msg = ref('')13// 获取页面上 DOM 元素的引用14const iptRef = ref(null)1516// 监听组件被创建和被激活的生命周期事件17onActivated(() => {18 console.log('Dark 组件被激活了!')19 // 让文本框自动获取光标焦点20 iptRef.value.focus()21})2223// 监听组件被缓存和被卸载的生命周期事件24onDeactivated(() => {25 console.log('Dark 组件被缓存起来了!')26})27</script>2829<style scoped>30.box {31 width: 500px;32 height: 300px;33 background-color: #2b2b2b;34 box-shadow: 1px 1px 10px #888;35 border-radius: 10px;36 padding: 10px;37 color: white;38}39</style>注意:只能在被 KeepAlive 的组件上使用 onActivated 和 onDeactivated 这两个 API。
我们可以通过传入 max prop 来限制可被缓存的最大组件实例数。<KeepAlive> 的行为在指定了 max 后类似一个 LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。
xxxxxxxxxx111<template>2 <button @click="comName = 'MyLight'">Light</button>3 <button @click="comName = 'MyDark'">Dark</button>4 <button @click="comName = 'MyCyan'">Cyan</button>56 <hr />78 <KeepAlive :max="2">9 <component :is="comName"></component>10 </KeepAlive>11</template><KeepAlive> 默认会缓存内部的所有组件实例,但我们可以通过 include 和 exclude prop 来定制该行为。其中:
这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组。
在指定 include 或 exclude 的值时,KeepAlive 会根据组件的 name 名称进行匹配。因此:
name 选项<script setup> 的 SFC 会自动根据文件名生成对应的 name 选项,无需再手动声明字符串类型的 include 值:
xxxxxxxxxx51<!-- 使用英文的逗号,分割多个组件的 name -->2<!-- include 表示这些组件需要被缓存,未包含在 include 中的组件将不会被缓存哦 -->3<KeepAlive include="ComLight,ComDark">4 <component :is="comName"></component>5</KeepAlive>字符串类型的 exclude 值(如果需要被缓存的组件很多,但是不需要被缓存的组件较少,此时可以使用 exclude 提高编码效率):
xxxxxxxxxx51<!-- 使用英文的逗号,分割多个组件的 name -->2<!-- exclude 表示这些组件不需要被缓存,未包含在 include 中的组件将会被缓存哦 -->3<KeepAlive exclude="ComCyan">4 <component :is="comName"></component>5</KeepAlive>正则类型的 include 值:
xxxxxxxxxx51<!-- 必须把 include 改为 v-bind 的形式 -->2<!-- 值为 /正则匹配/ 的格式 -->3<KeepAlive :include="/Com(Light|Dark)/">4 <component :is="comName"></component>5</KeepAlive>正则类型的 exclude 值:
xxxxxxxxxx51<!-- 必须把 exclude 改为 v-bind 的形式 -->2<!-- 值为 /正则匹配/ 的格式 -->3<KeepAlive :exclude="/.*Cyan/">4 <component :is="comName"></component>5</KeepAlive>数组类型的 include 值:
xxxxxxxxxx51<!-- 必须把 include 改为 v-bind 的形式 -->2<!-- 绑定的值为数组,数组中的元素项可以是字符串或正则 -->3<KeepAlive :include="['ComLight', /.*Dark/]">4 <component :is="comName"></component>5</KeepAlive>数组类型的 exclude 值:
xxxxxxxxxx51<!-- 必须把 exclude 改为 v-bind 的形式 -->2<!-- 绑定的值为数组,数组中的元素项可以是字符串或正则 -->3<KeepAlive :exclude="['ComLight', /.*Dark/]">4 <component :is="comName"></component>5</KeepAlive>整体思路分析:先实现组件的按需切换功能,再实现动态添加或移除被缓存的组件。
在 App.vue 中,导入需要用到的3个组件:
xxxxxxxxxx41// 导入需要的组件2import ComLight from './components/ComLight.vue'3import ComDark from './components/ComDark.vue'4import ComCyan from './components/ComCyan.vue'声明动态展示的组件列表,并使用索引来控制当前展示的组件:
xxxxxxxxxx61import { ref } from 'vue'23// 动态组件列表4const coms = [ComLight, ComDark, ComCyan]5// 要展示的组件索引6const index = ref(0)在模板中,使用 component 元素展示动态组件:
xxxxxxxxxx11<component :is="coms[index]"></component>点击不同的按钮,切换 index 的值,从而展示对应的组件:
xxxxxxxxxx31<button @click="index = 0">Light</button>2<button @click="index = 1">Dark</button>3<button @click="index = 2">Cyan</button>至此,组件的按需展示功能就已经实现啦。接下来,要着手实现动态添加和移除被缓存的组件。
首先,定义一个被缓存的组件名称的数组,它是一个响应式的数据:
xxxxxxxxxx21// 被缓存的组件名称的数组2const cacheNames = ref(['ComLight'])其次,在模板结构中用 KeepAlive 组件把 component 元素包裹起来,并使用 :include="cacheNames" 指定要缓存的组件:
xxxxxxxxxx31<KeepAlive :include="cacheNames">2 <component :is="coms[index]"></component>3</KeepAlive>接下来,定义把组件加入缓存和移除缓存的两个方法,它们都接收一个组件的 name 名称作为参数:
xxxxxxxxxx111// 添加新的组件缓存2const addCache = (name) => {3 if (cacheNames.value.includes(name)) return4 cacheNames.value.push(name)5}67// 移除已存在的缓存8const removeCache = (name) => {9 if (!cacheNames.value.includes(name)) return10 cacheNames.value = cacheNames.value.filter((item) => item !== name)11}最后,在模板结构中新增3个按钮,用来把组件加入缓存或移除缓存:
xxxxxxxxxx31<button @click="addCache('ComDark')">缓存ComDark</button>2<button @click="addCache('ComCyan')">缓存ComCyan</button>3<button @click="removeCache('ComLight')">不缓存ComLight</button>